home *** CD-ROM | disk | FTP | other *** search
/ EnigmA Amiga Run 1999 March / EnigmA AMIGA RUN 35 (1999)(G.R. Edizioni)(IT)[!][issue 1999-03].iso / earcd / -archivi / -recent2 / arexxcomm.lha / ARexxComm.e < prev    next >
Text File  |  1999-03-21  |  18KB  |  400 lines

  1. /* ARexxComm.e  v1.1 (21-03-99)  by Chris S Handley (Chris.S.Handley@BTInternet.com)
  2.  
  3.    Heavily based on ARexxPort.e v1.0 by Leon Woestenberg (leon@stack.urc.tue.nl);
  4.   a great source & top comments, but unreadable & not suited to modern programming
  5.   practices - basically used global vars for communication, plus could not
  6.   'drop in' to a program as a procedure required large modifications...
  7.  
  8.    I have spent a pretty long time restructuring it, checking logic, altering
  9.   & creating procedures, and so on - i.e.lots of work, so don't take this lightly!
  10.  
  11.    So, you can finally send/recieve arexx messages as a (fairly) simple OO
  12.   AmigaE Module.
  13.  
  14.  
  15.    Notes:
  16.   -The ARexxPort source was completely in the Public Domain, and so is ARexxComm .
  17.   -For those with a technical interest...
  18.    If you tell ackRexxMsg() that you didn't recognise the command, then a NEW
  19.    message is created that is sent to Rexx Master.  This new message contains a
  20.    pointer to the first message.  Now, when the new message is replied by Rexx
  21.    Master, we can remove the first message from memory (by using the pointer).
  22.    In this way, ARexx scripts can transparantly be used with ARexx commands.
  23.   -If you wish to ensure you don't cause crashes, please enable NILCHECK when
  24.    compiling both this & your own source code, as well as initialise all rxcom
  25.    object pointers to NIL when you declare them yourself.
  26.  
  27.    Here is a list of exceptions that are used:
  28.    exception    exceptioninfo    Reason
  29.    "LIB"    (library name)    Could not open library (can be disabled)
  30.    "PORT"    (port name)    Could not open ARexx port (can be disabled)
  31.    "RXNA"    (rexx command)    Did not acknowledge previous ARexx message
  32.    "RXNM"    -        No ARexx message to
  33.    "RXCM"    (description)    Method object-use error - details supplied.
  34. */
  35.  
  36. OPT MODULE
  37.  
  38. MODULE 'exec/ports','exec/nodes'
  39. MODULE 'rexxsyslib','rexx/rexxio','rexx/rxslib','rexx/errors','rexx/storage'
  40. MODULE 'dos/dos'
  41.  
  42. EXPORT OBJECT rxcom PRIVATE
  43.     hostport:PTR TO mp        ->pointer to our host's port
  44.     unconfirmed            ->counts unconfirmed messages sent by user
  45.     allunconfirmed            ->counts all unconfirmed messages sent
  46.     useexceptions            ->indicates ONLY if we should use exceptions for failure to open an ARexx port
  47.     rexxmsgtoreply:PTR TO rexxmsg    ->pointer to the message we have yet to reply to
  48.     cmdtoreply:PTR TO CHAR        ->the command of the message not yet replied to
  49.     argstoreply:PTR TO CHAR        ->the args of the message not yet replied to
  50. ENDOBJECT
  51.  
  52. ->Acknowledge a message was handled
  53. EXPORT PROC ackRexxMsg(commandknown:LONG) OF rxcom
  54.  DEF rexxmsg:PTR TO rexxmsg,    ->pointer to handled message
  55.      rexxargs:PTR TO LONG    ->list of 16 argument pointers (inc.command strings)
  56.  
  57.     IF self.cmdtoreply
  58.         rexxmsg:=self.rexxmsgtoreply    ->get rexxmsg value from waitForRexxMsg()
  59.  
  60.         IF commandknown            ->act on whether command is known or not
  61.             ReplyMsg(rexxmsg)    ->reply that 'message was recieved'
  62.         ELSE
  63.              /* Send unknown commands to Rexx Master (so can call program from a rexx script) */
  64.  
  65.              /* Forward this command to Rexx Master, but the original unknown
  66.                command (message) is not replied to until we recieve a
  67.                confirmation from Rexx Master.
  68.  
  69.                Therefore we remember this message by storing a messagepointer
  70.                in rexxargs[15] of the forwarded command to Rexx Master */
  71.  
  72.             rexxargs:=rexxmsg.args                    ->set pointer to arguments
  73.             IF self.sendRexxMsg_Add('REXX',rexxargs[0],rexxmsg,0)=NIL    ->failed to send message? (Rexx Master does not exist?)
  74.                 cantAckRexxMsg(rexxmsg,RC_FATAL,NIL)        ->set fatal error return code & result string
  75.                 ReplyMsg(rexxmsg)                ->reply that 'message was recieved' (with return code & result string)
  76.             ENDIF
  77.         ENDIF
  78.  
  79.         DisposeLink(self.cmdtoreply); self.cmdtoreply:=NIL
  80.         IF self.argstoreply
  81.             DisposeLink(self.argstoreply); self.argstoreply:=NIL
  82.         ENDIF
  83.     ELSE
  84.         Raise("RXNM")            ->error: no message (to reply to)!
  85.     ENDIF
  86. ENDPROC
  87.  
  88. ->set return code & result string of an ARexx message
  89. PROC cantAckRexxMsg(rexxmsg:PTR TO rexxmsg,rc,resultstring)
  90.      /* set the return code */
  91.     rexxmsg.result1:=rc
  92.  
  93.      /* a pointer to the result string */
  94.     IF (rexxmsg.action AND RXFF_RESULT) AND (resultstring<>NIL)
  95.         rexxmsg.result2:=CreateArgstring(resultstring,StrLen(resultstring))
  96.     ELSE
  97.         rexxmsg.result2:=NIL
  98.     ENDIF
  99. ENDPROC
  100.  
  101. ->tell how many outstanding acknowledgements there are
  102. EXPORT PROC howManyAcksLeft() OF rxcom
  103.     RETURN self.unconfirmed
  104. ENDPROC
  105.  
  106. ->wait for an ARexx message with a real command, and returns that command and/or notification of a Ctrl-C
  107. EXPORT PROC waitForRexxMsg(tellofack=FALSE) OF rxcom
  108.  DEF signalmask=0, hostmask=0, command=NIL:PTR TO CHAR, args=NIL:PTR TO CHAR,
  109.      rexxmsg=NIL:PTR TO rexxmsg,
  110.      quit=FALSE, busywait=TRUE,                ->note we never REALLY "busy wait" if busywait=TRUE - but we DO directly poll the list until it is empty
  111.      wasack=FALSE
  112.  
  113.     hostmask:=Shl(1,self.hostport.sigbit)        ->signalmask for our host port
  114.  
  115.     IF self.cmdtoreply=NIL
  116.          /* get a valid command to be returned for the host */
  117.         REPEAT
  118.             IF busywait            ->see if there's a good chance of another message (i.e.we just got one before) then...
  119.                             ->...directly poll for another message, else...
  120.                 rexxmsg:=GetMsg(self.hostport)            ->(try to) get next message
  121.                 IF rexxmsg                    ->see if there really was a message
  122.                     command, args, wasack:=self.handleRexxMsg({rexxmsg}) ->handle this message
  123.                 ELSE
  124.                     busywait:=FALSE                ->no more messages currently in list, so don't busywait!
  125.                 ENDIF
  126.             ELSE
  127.                             ->...get OS to wake us when a new message arrives
  128.                 signalmask:=Wait(hostmask OR SIGBREAKF_CTRL_C)    ->wait for event signals (either message for us or a Ctrl-C)
  129.                 IF signalmask AND hostmask            ->act on a message signal
  130.                     rexxmsg:=GetMsg(self.hostport)        ->get first message
  131.                     command, args, wasack:=self.handleRexxMsg({rexxmsg}) ->handle this event
  132.                 ENDIF
  133.                 IF signalmask AND SIGBREAKF_CTRL_C THEN quit:=TRUE ->act on a quit signal
  134.             ENDIF
  135.         UNTIL command OR quit OR (wasack AND tellofack)    ->repeat if last message was a confirmation reply to a message from us, unless caller wants to know about it
  136.  
  137.         IF wasack AND tellofack                ->if msg was just an acknowledge then...
  138.             command:=NIL                ->...ensure caller can't read any useless info...
  139.             args:=NIL
  140.         ELSE                        ->...else store valid message (so know this needs acknolwedging)
  141.             self.rexxmsgtoreply:=rexxmsg        ->store rexxmsg (pointer) for replyToRexxMsg()
  142.             self.argstoreply:=args            ->record of String() to DisposeLink() of
  143.             self.cmdtoreply:=command        ->show that stored rexxmsg not yet replied to (& record of String() to DisposeLink() of)
  144.         ENDIF
  145.  
  146.         IF quit AND command THEN WriteF('WARNING:  waitForRexxMsg(); Ctrl-C & a command recieved simultaneously; was assumed impossible, so Ctrl-C has been safely ignored.  Please notify author of ARexxComm (Chris.S.Handley@BTInternet.com).\n')    ->should never happen (I guess)
  147.     ELSE
  148.         Throw("RXNA",self.cmdtoreply)        ->error: didn't reply to last message!
  149.     ENDIF
  150. ENDPROC command, args, wasack
  151.  
  152. ->poll for an ARexx message with a real command, and returns that command (may =NIL)
  153. EXPORT PROC pollForRexxMsg(tellofack=FALSE) OF rxcom
  154.  DEF hostmask=0, command=NIL:PTR TO CHAR, args=NIL:PTR TO CHAR,
  155.      rexxmsg=NIL:PTR TO rexxmsg, old_rexxmsg=NIL:PTR TO rexxmsg,
  156.      wasack=FALSE
  157.  
  158.     hostmask:=Shl(1,self.hostport.sigbit)        ->signalmask for our host port
  159.  
  160.     IF self.cmdtoreply=NIL
  161.          /* get a valid command to be returned for the host, if one exists */
  162.         REPEAT
  163.              /* directly poll for a message */
  164.             rexxmsg:=GetMsg(self.hostport)            ->(try to) get next message
  165.             old_rexxmsg:=rexxmsg
  166.             IF rexxmsg                    ->see if there really was a message
  167.                 command, args, wasack:=self.handleRexxMsg({rexxmsg}) ->handle this message
  168.             ENDIF
  169.         UNTIL (rexxmsg=old_rexxmsg) OR (wasack AND tellofack) ->repeat if last message was a confirmation reply to a message from us, unless caller wants to know about it
  170.         
  171.         IF wasack AND tellofack                ->if msg was just an acknowledge then...
  172.             command:=NIL                ->...ensure caller can't read any useless info...
  173.             args:=NIL
  174.         ELSE                        ->...else store valid message (so know this needs acknolwedging)
  175.             self.rexxmsgtoreply:=rexxmsg        ->store rexxmsg (pointer) for replyToRexxMsg()
  176.             self.argstoreply:=args            ->record of String() to DisposeLink() of
  177.             self.cmdtoreply:=command        ->show that stored rexxmsg not yet replied to (& record of String() to DisposeLink() of)
  178.         ENDIF
  179.     ELSE
  180.         Throw("RXNA",self.cmdtoreply)        ->error: didn't reply to last message!
  181.     ENDIF
  182. ENDPROC command, args, wasack
  183.  
  184. ->examine message & deal with if was a reply to something we sent (& nullify rexxmsg since was dealt with), else return command
  185. PROC handleRexxMsg(rexxmsgptr:PTR TO LONG) OF rxcom     ->rexxmsgptr=address of pointer to handled message
  186.  DEF rexxmsg=NIL:PTR TO rexxmsg,->pointer to handled message; dereferenced rexxmsgptr
  187.      msgnode:PTR TO mn,        ->pointer to messagenode
  188.      listnode:PTR TO ln,    ->pointer to listnode of message
  189.      rexxargs:PTR TO LONG,    ->list of 16 argument pointers (inc.command strings)
  190.      command=NIL:PTR TO CHAR,    ->points to command string
  191.      args=NIL:PTR TO CHAR,    ->points to arguments string
  192.      wasack=FALSE,        ->says whether message was an acknowledgement of our message or not
  193.      cmdstart, cmdend, argsstart, argsend, string:PTR TO CHAR
  194.  
  195.     rexxmsg:=^rexxmsgptr
  196.     IF rexxmsg                ->could be called with no message to handle! (don't think should ever happen, but...)
  197.         msgnode:=rexxmsg.mn        ->get pointer to messagenode
  198.         listnode:=msgnode.ln        ->get pointer to listnode
  199.         rexxargs:=rexxmsg.args        ->get pointer to commands
  200.  
  201.         IF listnode.type=NT_REPLYMSG        ->if confirmation reply of a message sent by us then...
  202.                             ->...deal automatically with our reply...
  203.             IF rexxargs[15]                ->see if this is a reply to a forwarded message then...
  204.                 ReplyMsg(rexxargs[15])        ->...finally reply to original sender that message was handled
  205.             ELSE
  206.                 self.unconfirmed:=self.unconfirmed-1    ->decrease unconfirmed message-recieved counter
  207.                 wasack:=TRUE    ->make so return indicating message was just a reply/acknowledge (ignore ack of commands not known to our user)
  208.             ENDIF
  209.             self.allunconfirmed:=self.allunconfirmed-1    ->decrease unconfirmed message-recieved counter
  210.             DeleteArgstring(rexxargs[0])        ->deallocate this confirmation message object
  211.             ->ClearRexxMsg(rexxmsg, 16)        ->### this may be useful? ###
  212.             DeleteRexxMsg(rexxmsg); rexxmsg:=NIL; ^rexxmsgptr:=NIL
  213.             ->rexxmsg is NIL; tells caller message was of no use
  214.         ELSEIF listnode.type=NT_MESSAGE        ->...else if can read then...*/
  215.                             ->...extract & return the command + args sent to us in this brand new message...
  216.              /* extract command (1st word) & args (later words) from rexxargs[0] string */
  217.             string:=rexxargs[0]
  218.              /* find start of command after skipping spaces/etc. */
  219.             cmdstart:=TrimStr(string)-string
  220.             IF string[cmdstart]<>"\0"
  221.                  /* find end of command (1st space) */
  222.                 cmdend:=cmdstart; REPEAT
  223.                     cmdend++
  224.                 UNTIL (string[cmdend]="\0") OR (string[cmdend]=" ")
  225.                 argsstart:=cmdend
  226.                 cmdend--
  227.                 ->*have command
  228.                 command:=String(cmdend-cmdstart+1) ->create command string
  229.                 MidStr(command,string,cmdstart,cmdend-cmdstart+1) ->copy in command part
  230.                 IF string[argsstart]=" "
  231.                      /* find start of args after skipping spaces/etc. */
  232.                     REPEAT
  233.                         argsstart++
  234.                     UNTIL (string[cmdend]="\0") OR (string[cmdend]<>" ")
  235.                     IF string[argsstart]<>"\0"
  236.                          /* find end of args (end of string) */
  237.                         argsend:=argsstart; REPEAT
  238.                             argsend++
  239.                         UNTIL string[argsend]="\0"
  240.                         argsend--
  241.                         ->*have args
  242.                         args:=String(argsend-argsstart+1)    ->create command string
  243.                         MidStr(args,string,argsstart,argsend-argsstart+1) ->copy in args part
  244.                     ELSE
  245.                         ->*no args
  246.                         args:=String(1)        ->create an empty args string
  247.                     ENDIF
  248.                 ELSE
  249.                     ->*no args
  250.                     args:=String(1)            ->create an empty args string
  251.                 ENDIF
  252.             ELSE
  253.                 ->*no command (or args)
  254.                 command:=String(1)            ->create an empty command string
  255.                 args:=String(1)                ->create an empty args string
  256.             ENDIF
  257.             rexxmsg.result1:=0            ->init. the resultcode
  258.             rexxmsg.result2:=NIL            ->init. the resultstring
  259.         ELSE                    ->...else message type is unknown (ought to be type NT_UNKNOWN)...
  260.                             ->...so completely ignore it - must definitely not reply to!
  261.             rexxmsg:=NIL; ^rexxmsgptr:=NIL    ->rexxmsg is NIL; tells caller message was of no use
  262.         ENDIF
  263.     ENDIF
  264. ENDPROC command, args, wasack
  265.  
  266. ->send a message to some target ARexx port
  267. EXPORT PROC sendRexxMsg(targetname:PTR TO CHAR, command:PTR TO CHAR, flags=0) OF rxcom
  268.     IF targetname=NIL THEN Throw("RXCM",'sendRexxMsg(); targetname was NIL')
  269.     IF command=NIL THEN Throw("RXCM",'sendRexxMsg(); command was NIL')
  270.  
  271.     RETURN self.sendRexxMsg_Add(targetname,command,NIL,flags)
  272. ENDPROC
  273.  
  274. ->send a message to some target ARexx port - with an Added pointer as the last argument
  275. PROC sendRexxMsg_Add(targetname:PTR TO CHAR, command:PTR TO CHAR, unknownmsg=NIL, flags=0) OF rxcom
  276.  DEF targetport=NIL:PTR TO mp, rexxmsg=NIL:PTR TO rexxmsg, rexxargs=NIL:PTR TO LONG, hostport=NIL:PTR TO mp,
  277.      listnode=NIL:PTR TO ln, argstring=NIL, cmdlen
  278.  
  279.     IF targetname=NIL THEN Throw("RXCM",'sendRexxMsg_Add(); targetname was NIL')
  280.     IF command=NIL THEN Throw("RXCM",'sendRexxMsg_Add(); command was NIL')
  281.  
  282.     hostport:=self.hostport
  283.     listnode:=hostport.ln
  284.     IF (rexxmsg:=CreateRexxMsg(hostport,'rexx',listnode.name))=NIL THEN RETURN NIL    ->tell if could not send message; N.B.CreateRexxMsg() sets .replyport for us
  285.     rexxargs:=rexxmsg.args            ->pointer to commands
  286.  
  287.     cmdlen:=StrLen(command)
  288.     IF argstring:=CreateArgstring(command,cmdlen+3)            ->was the argstring created?
  289.         argstring[cmdlen]:="\0"        ->ensure null terminated!
  290.         rexxargs[0]:=argstring        ->set the first argstring
  291.         rexxmsg.action:=RXCOMM OR flags    ->set the flags
  292.         rexxargs[15]:=unknownmsg    ->store original message pointer into 16th argstring pointer for handleRexxMsg()
  293.  
  294.         Forbid()            ->forbid multitasking (ouch!)
  295.         IF (targetport:=FindPort(targetname)) THEN PutMsg(targetport,rexxmsg)    ->send our message to an existing port
  296.         Permit()            ->permit multitasking (phew!)
  297.  
  298.         IF targetport            ->check if message was sent
  299.             IF unknownmsg=NIL THEN self.unconfirmed:=self.unconfirmed+1    ->increase the unconfirmed message-recieved counter (for messages sent by user)
  300.             self.allunconfirmed:=self.allunconfirmed+1    ->increase unconfirmed message-recieved counter
  301.             RETURN rexxmsg        ->successfully sent this message
  302.         ENDIF
  303.     ELSE
  304.         DeleteRexxMsg(rexxmsg)
  305.         RETURN NIL                                ->could not send message
  306.     ENDIF
  307.  
  308.      /* although rexxmsg & argstring should exist, this is a catch-all just-in-case, so still checks if exists */
  309.     IF argstring
  310.         DeleteArgstring(argstring)
  311.     ENDIF
  312.     IF rexxmsg
  313.         DeleteRexxMsg(rexxmsg)
  314.     ENDIF
  315.     RETURN NIL
  316. ENDPROC
  317.  
  318. ->see if a port exists
  319. EXPORT PROC portExists(portname:PTR TO CHAR) OF rxcom
  320.     IF portname=NIL THEN Throw("RXCM",'portExists(); portname was NIL')
  321.  
  322.     RETURN IF FindPort(portname) THEN TRUE ELSE FALSE
  323. ENDPROC
  324.  
  325. ->create a port for our host
  326. EXPORT PROC createPort(portname:PTR TO CHAR,useexceptionsoraddr,priority=0) OF rxcom
  327.  DEF hostport=NIL:PTR TO mp, node=NIL:PTR TO ln
  328.  
  329.     IF portname=NIL THEN Throw("RXCM",'createPort(); portname was NIL')
  330.     IF useexceptionsoraddr=FALSE THEN Throw("RXCM",'createPort(); useexceptionsoraddr was FALSE')
  331.  
  332.     IF (rexxsysbase:=OpenLibrary('rexxsyslib.library',0))
  333.         IF portname        ->create named (public) port?
  334.             Forbid()    ->ensure no-one makes a port of the same name while we are making ours
  335.             IF FindPort(portname)=0            ->ensure port name does not already exist
  336.                 IF hostport:=CreateMsgPort()    ->if created port
  337.                     node:=hostport.ln
  338.                     node.name:=portname    ->set name
  339.                     node.pri:=priority    ->set public port priority
  340.                     AddPort(hostport)    ->make this port public (add to public port list)
  341.                 ENDIF
  342.             ENDIF
  343.             Permit()
  344.         ELSE
  345.             hostport:=CreateMsgPort()        ->(try) to create an unnamed (private) port
  346.         ENDIF
  347.  
  348.         IF hostport                    ->create port info block (needed by ARexxCom) if port created
  349.             self.hostport:=hostport
  350.             self.unconfirmed:=0
  351.             self.allunconfirmed:=0
  352.             self.useexceptions:=(IF useexceptionsoraddr=TRUE THEN TRUE ELSE FALSE)
  353.             self.rexxmsgtoreply:=NIL
  354.             self.cmdtoreply:=NIL
  355.             self.argstoreply:=NIL
  356.         ELSE
  357.             self.end()                ->a little trick!
  358.             IF useexceptionsoraddr=TRUE THEN Throw("PORT",portname)    ->warn that port was not opened
  359.             ^useexceptionsoraddr:=NIL
  360.         ENDIF
  361.     ELSE
  362.         IF useexceptionsoraddr=TRUE THEN Throw("LIB",'rexxsyslib.library')    ->warn that library could not be opened
  363.         ^useexceptionsoraddr:=NIL
  364.     ENDIF
  365. ENDPROC
  366.  
  367. ->destroy the port of our host
  368. EXPORT PROC end() OF rxcom
  369.  DEF listnode=NIL:PTR TO ln, msg=NIL:PTR TO mn/*rexxmsg*/, hostport:PTR TO mp,
  370.      command=NIL:PTR TO CHAR,args=NIL:PTR TO CHAR, wasack=FALSE
  371.  
  372.     hostport:=self.hostport
  373.     IF hostport                        ->sanity check port pointer
  374.         listnode:=hostport.ln
  375.         IF listnode.name THEN RemPort(hostport)        ->if public then remove from public port list (so no new messages)
  376.  
  377.         WHILE self.allunconfirmed>0            ->now wait till everyone expecting the port to be there has sent acknowledgement
  378.             command,args,wasack:=self.waitForRexxMsg(TRUE)
  379.             IF command THEN self.ackRexxMsg(TRUE)    ->(this shouldn't ever happen now!) pretend to know about message so we can exit quickly
  380.         ENDWHILE
  381.  
  382.         self.hostport:=NIL                ->indicate now a totally non-ARexx & private port
  383.         Forbid()                    ->no more messages while...
  384.         WHILE msg:=GetMsg(hostport)            ->...acknowledge each message, and...
  385.             ->IF msg.mn.ln.type AND NT_MESSAGE THEN ReplyMsg(msg)    ->ought to work as below, but needs "msg=NIL:PTR TO rexxmsg"; does not work for some reason :(
  386.             IF msg.replyport<>hostport        ->reply "recieved" if did not send it...
  387.                 ReplyMsg(msg)             ->needs "msg=NIL:PTR TO mn"
  388.             /*ELSE
  389.                 ->I think OUGHT to delete rexx msg, but not done in orig.src code plus need "msg=NIL:PTR TO rexxmsg"*/
  390.             ENDIF
  391.         ENDWHILE
  392.         DeleteMsgPort(hostport)                ->...delete the port
  393.         Permit()
  394.  
  395.         IF rexxsysbase                    ->if library open then close it
  396.             CloseLibrary(rexxsysbase); rexxsysbase:=NIL
  397.         ENDIF
  398.     ENDIF
  399. ENDPROC
  400.